/*
 * Copyright 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.keyczar.rsa;

import com.google.gson.annotations.Expose;

import org.keyczar.DefaultKeyType;
import org.keyczar.Keyczar;
import org.keyczar.KeyczarPublicKey;
import org.keyczar.enums.RsaPadding;
import org.keyczar.exceptions.KeyczarException;
import org.keyczar.exceptions.UnsupportedTypeException;
import org.keyczar.interfaces.KeyType;
import org.keyczar.interfaces.Stream;
import org.keyczar.util.Util;

import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.Signature;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPublicKeySpec;


/**
 * Wrapping class for RSA Public Keys suitable for verifying. These must be exported from existing
 * RSA private key sets.
 *
 * @author steveweis@gmail.com (Steve Weis)
 */
public class RsaPublicSigningKey extends KeyczarPublicKey {

  private RSAPublicKey jcePublicKey;
  @Expose final String modulus;
  @Expose final String publicExponent;
  @Expose final String signatureAlgorithm;
  @Expose final String mgf;

  private final byte[] hash = new byte[Keyczar.KEY_HASH_SIZE];

  public static RsaPublicSigningKey read(String input) throws KeyczarException {
    RsaPublicSigningKey key = Util.gson().fromJson(input, RsaPublicSigningKey.class);

    if (key.getType() != DefaultKeyType.RSA_PUB_SIGN) {
      throw new UnsupportedTypeException(key.getType());
    }
    return key.initFromJson();
  }

  @Override
  public byte[] hash() {
    return hash;
  }

  @Override
  protected Stream getStream() throws KeyczarException {
    try {
      return new RsaVerifyingStream(Signature.getInstance(
          SignatureAlgorithm.getSignatureAlgorithm(signatureAlgorithm, mgf)), jcePublicKey);
    } catch (GeneralSecurityException ex) {
      throw new KeyczarException(ex);
    }
  }

  @Override
  public KeyType getType() {
    return DefaultKeyType.RSA_PUB_SIGN;
  }
  
  RsaPublicSigningKey(RSAPrivateCrtKey privateKey, String signatureAlgorithm, String mgf) throws KeyczarException {
    super(privateKey.getModulus().bitLength());
    this.modulus = Util.encodeBigInteger(privateKey.getModulus());
    this.publicExponent = Util.encodeBigInteger(privateKey.getPublicExponent());
    this.signatureAlgorithm = signatureAlgorithm;
    this.mgf = mgf;
    initializeJceKey(privateKey.getModulus(), privateKey.getPublicExponent());
    initializeHash();
  }

  // Used by GSON, which will overwrite the values set here.
  private RsaPublicSigningKey() {
    super(0);
    modulus = publicExponent = null;
    mgf = null;
    signatureAlgorithm = null;
  }

  /**
   * Initialize JCE key from JSON data.  Must be called after an instance is read from JSON.
   * In default scope so {@link RsaPrivateKey} can call it when a private key string (which
   * contains a public key string) is deserialized.
   */
  RsaPublicSigningKey initFromJson() throws KeyczarException {
    initializeJceKey(Util.decodeBigInteger(modulus), Util.decodeBigInteger(publicExponent));
    initializeHash();
    return this;
  }

  private void initializeJceKey(BigInteger publicModulus, BigInteger publicExponent)
      throws KeyczarException {
    try {
      RSAPublicKeySpec spec = new RSAPublicKeySpec(publicModulus, publicExponent);
      jcePublicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(spec);
    } catch (GeneralSecurityException e) {
      throw new KeyczarException(e);
    }
  }

  private void initializeHash() throws KeyczarException {
    System.arraycopy(RsaPadding.OAEP.computeFullHash(jcePublicKey), 0, hash, 0, hash.length);
  }

  int keySizeInBytes() {
    return jcePublicKey.getModulus().bitLength() / 8;
  }

  @Override
  protected RSAPublicKey getJceKey() {
    return jcePublicKey;
  }

  @Override
  protected boolean isSecret() {
    return false;
  }
}
